package com.treepoke.util.location;

import com.treepoke.util.exception.UtilException;
import com.treepoke.util.properties.TPConstant;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.sql.DataSource;

/**
 * Clase que permite ubicar las conexiones a base de datos y las instancias de
 * EJBs por JNDI dentro de un contenedor.
 */
public class ServiceLocator {

    private static ServiceLocator instance;
    private Context ctx;
    private Context remoteCtx;
    private HashMap<String, Object> cache;

    private ServiceLocator() throws UtilException {
        cache = new HashMap<String, Object>();
        getContext();
        //getRemoteContext();
    }

    /**
     * Método que recupera la instancia del
     * <code>ServiceLocator</code>.
     *
     * @return
     * @throws UtilException
     */
    static public ServiceLocator getInstance() throws UtilException {
        if (instance == null) {
            instance = new ServiceLocator();
        }

        return instance;
    }

    /**
     * Método que recupera
     * <code>DataSource</code> del contexto.
     *
     * @param nombreDataSource
     * @return
     * @throws NamingException
     */
    public DataSource getDataSource(String nombreDataSource) throws UtilException {
        DataSource ds = (DataSource) cache.get(nombreDataSource);
        if (ds == null) {
            try {
                ds = (DataSource) ctx.lookup(nombreDataSource);
                cache.put(nombreDataSource, ds);
            } catch (NamingException ex) {
                throw new UtilException("Error buscando recurso: " + nombreDataSource, ex, 0);
            }
        }

        return ds;
    }

    /**
     * Obtiene la conexién al DS enviado como parémetro
     *
     * @return
     * @throws NamingException, SQLException
     */
    public Connection getConnection(String nombreDataSource) throws UtilException {
        try {
            DataSource ds = getDataSource(nombreDataSource);
            return ds.getConnection();
        } catch (SQLException ex) {
            throw new UtilException("Error obteniendo conexión: " + ex.getMessage(), ex, 0);
        }
    }

    /**
     * Metodo que obtiene de un contenedor EJB
     *
     * @param nameReference
     * @return
     * @throws NamingException
     */
    public Object getEJB(String nameReference) throws UtilException {
        return lookup(nameReference);
    }
    
    /**
     * Metodo que obtiene un objeto registrado en el árbol JNDI
     * @param nameReference
     * @return
     * @throws NamingException
     */
    public Object lookup(String jndiName) throws UtilException {
        try {
            return ctx.lookup(jndiName);
        } catch (NamingException ex) {
            throw new UtilException("Error buscando recurso: " + jndiName, ex, 0);
        }
    }
    
    /**
     * Enlaza el objeto al árbol JNDI según el nombre
     * @param jndiName
     * @param object
     * @throws UtilException 
     */
    public void bind(String jndiName, Object object) throws UtilException {
        try {
            ctx.bind(jndiName, object);
        } catch (NamingException ex) {
            throw new UtilException("Error enlazando recurso: " + jndiName, ex, 0);
        }
    }
    
    /**
     * Reenlaza el objeto al árbol JNDI según el nombre
     * @param jndiName
     * @param object
     * @throws UtilException 
     */
    public void rebind(String jndiName, Object object) throws UtilException {
        try {
            ctx.rebind(jndiName, object);
        } catch (NamingException ex) {
            throw new UtilException("Error reenlazando recurso: " + jndiName, ex, 0);
        }
    }    
    
    /**
     * Desenlaza el objeto al árbol JNDI según el nombre
     * @param jndiName
     * @throws UtilException 
     */
    public void unbind(String jndiName) throws UtilException {
        try {
            ctx.unbind(jndiName);
        } catch (NamingException ex) {
            throw new UtilException("Error desenlazando recurso: " + jndiName, ex, 0);
        }
    }
    
    
    /**
     * Metodo que obtiene un objeto remoto registrado en el árbol JNDI
     * @param nameReference
     * @return
     * @throws NamingException
     */
    public Object lookupRemote(String jndiName, Class clase) throws UtilException {
        try {
            return PortableRemoteObject.narrow(remoteCtx.lookup(jndiName), clase);
        } catch (NamingException ex) {
            throw new UtilException("Error buscando recurso: " + jndiName, ex, 0);
        }
    }
    
    /**
     * Enlaza el objeto al árbol JNDI según el nombre
     * @param jndiName
     * @param object
     * @throws UtilException 
     */
    public void bindRemote(String jndiName, Object object) throws UtilException {
        try {
            remoteCtx.bind(jndiName, object);
        } catch (NamingException ex) {
            throw new UtilException("Error enlazando recurso: " + jndiName, ex, 0);
        }
    }
    
    /**
     * Reenlaza el objeto al árbol JNDI según el nombre
     * @param jndiName
     * @param object
     * @throws UtilException 
     */
    public void rebindRemote(String jndiName, Object object) throws UtilException {
        try {
            remoteCtx.rebind(jndiName, object);
        } catch (NamingException ex) {
            throw new UtilException("Error reenlazando recurso: " + jndiName, ex, 0);
        }
    }    
    
    /**
     * Desenlaza el objeto al árbol JNDI según el nombre
     * @param jndiName
     * @throws UtilException 
     */
    public void unbindRemote(String jndiName) throws UtilException {
        try {
            remoteCtx.unbind(jndiName);
        } catch (NamingException ex) {
            throw new UtilException("Error desenlazando recurso: " + jndiName, ex, 0);
        }
    }    
    
    /**
     * Metodo que cierra la conexión a la base de datos
     *
     * @param connection Define la conexión que debe cerrarse
     * @throws SQLException
     */
    public void closeConnection(Connection connection) throws UtilException {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException ex) {
                throw new UtilException("Error cerrando conexión: " + ex.getMessage(), ex, 0);
            }
        }
    }

    /**
     * 
     * @return
     * @throws NamingException 
     */
    public final Context getContext() throws UtilException {
        if (ctx == null) {
            try {
                ctx = new InitialContext();
            } catch (NamingException ex) {
                throw new UtilException("Error creando contexto: " + ex.getMessage(), ex, 0);
            }
        }

        return ctx;
    }
    
    /**
     * 
     * @return
     * @throws NamingException 
     */
    public final Context getRemoteContext() throws UtilException {
        if (remoteCtx == null) {
            try {                
                Hashtable<String, String> env = new Hashtable<String, String>();

                env.put(Context.INITIAL_CONTEXT_FACTORY, TPConstant.getINITIAL_CONTEXT_FACTORY());
                env.put(Context.PROVIDER_URL, TPConstant.getPROVIDER_URL());
                env.put(Context.SECURITY_PRINCIPAL, TPConstant.getSECURITY_PRINCIPAL());
                env.put(Context.SECURITY_CREDENTIALS, TPConstant.getSECURITY_CREDENTIALS());

                remoteCtx = new InitialContext(env);
            } catch (NamingException ex) {
                throw new UtilException("Error creando contexto: " + ex.getMessage(), ex, 0);
            }
        }

        return remoteCtx;
    }    

    public void finalizar() {
        instance = null;
    }
    
}
